use std::{collections::HashMap, fs::File, io::Read, path::Path};

use common_base::{
    once_cell::sync::OnceCell,
    prelude::*,
    sqlx::{Connection, MySqlConnection},
    toml,
};

use crate::data::ParamObject;

use super::{db_account::DbAccount, db_type::DbType, DbConnection};

static DB_CONFIG_MAP: OnceCell<HashMap<String, DbAccount>> = OnceCell::new();
static DEFAULT_DB_ID: OnceCell<String> = OnceCell::new();

pub struct DbConnManager;

impl DbConnManager {
    pub fn initialize(db_config_file: &str) -> Result<()> {
        let mut file = File::open(Path::new(db_config_file)).map_err(|e| ErrorCode::from(e))?;
        let mut db_config_content = String::new();
        file.read_to_string(&mut db_config_content)
            .map_err(|e| ErrorCode::from(e))?;
        let mut db_configs_in_map: HashMap<String, Vec<DbAccount>> =
            toml::from_str(&db_config_content).map_err(|e| ErrorCode::from(e))?;
        let db_configs = db_configs_in_map
            .remove("db_config")
            .expect("db_config file format invalid");

        // find the first db_account with `default = true` as default db account
        if let Some(default_acct) = db_configs.iter().find(|db_acct| db_acct.default) {
            DEFAULT_DB_ID
                .set(default_acct.id.clone())
                .expect("DEFAULT_DB_ID can not be initialize twice");
        } else {
            return Err(ErrorCode::DbConfigNotFound(
                "can not find default db_account",
            ));
        }

        // save db_acount in static
        let mut db_config_map = HashMap::with_capacity(db_configs.len());
        db_configs.into_iter().for_each(|db_config| {
            db_config_map.insert(db_config.id.to_string(), db_config);
        });
        DB_CONFIG_MAP
            .set(db_config_map)
            .expect("DB_CONFIG_MAP can not be initialize twice");

        Ok(())
    }

    pub async fn get_connection(param_object: &mut ParamObject) -> Result<DbConnection> {
        let _db_convert_key = param_object.get_value_by_env_name("DB_CONVERT_KEY");

        // TODO: get DbParamManager instance and convertDbParam
        let db_param_in_po = param_object.get_value_by_env_name("ESP_DBParam").eq("1");
        let db_account = if db_param_in_po {
            // let db_type = param_object.get_value_by_env_name("DataBaseType");
            // if !db_type.is_empty() {
            //     ...
            // }
            None
        } else {
            // use user profile saved in session, if UserName in PO is same with in user profile, then copy UserPass from profile to PO
            // This is dummy
            param_object.set_value_by_env_name("UserPass", "changeme".to_owned());

            // if no DBName and DBNO in PO, set default db_acount id in PO
            // TODO: if eaiServer exist, is this correct?
            if param_object
                .get_value_by_env_name("DataBaseName")
                .is_empty()
                || param_object.get_value_by_env_name("DBNO").is_empty()
            {
                let db_id = DEFAULT_DB_ID
                    .get()
                    .expect("DEFAULT_DB_ID has not been initialized");
                param_object.set_value_by_env_name("DataBaseName", db_id.to_owned());
                param_object.set_value_by_env_name("DBNO", db_id.to_owned());
            }

            // find db_acount and set dbOwer to PO
            let db_name = param_object.get_value_by_env_name("DataBaseName");
            let db_no = param_object.get_value_by_env_name("DBNO");
            if let Some((_, db_account)) = DB_CONFIG_MAP
                .get()
                .expect("DB_ACCOUNT_MAP has not been initialized")
                .iter()
                .find(|(db_id, _)| db_name.eq(db_id.as_str()) && db_no.eq(db_id.as_str()))
            {
                param_object.set_value_by_env_name("dbOwner", db_account.username.clone());
                Some(db_account)
            } else {
                return Err(ErrorCode::DbConfigNotFound(format!(
                    "can not find db_config with db_name: {}, db_no: {}",
                    db_name, db_no
                )));
            }
        };

        Self::init_connection(DbConnection::new(DbType::MySQL), param_object, db_account).await
    }

    async fn init_connection(
        mut db_connection: DbConnection,
        param_object: &mut ParamObject,
        db_account: Option<&DbAccount>,
    ) -> Result<DbConnection> {
        let threshold = param_object
            .get_value_by_param_name("ROW_COUNT_THRESHOLD")
            .parse::<i32>()
            .unwrap_or(-1);
        if threshold > 0 {
            db_connection.threshold = threshold;
        }

        let _product = param_object.get_value_by_env_name("Product");
        // TODO: if product is not empty, invoke iom to initConn

        let (db_name, db_url) = if param_object.get_value_by_env_name("ESP_DBParam").eq("1") {
            let db_host = param_object.get_value_by_env_name("DbHost");
            let db_port = param_object.get_value_by_env_name("DbPort");
            let db_name = param_object.get_value_by_env_name("DataBaseName");
            let charset = param_object.get_value_by_env_name_or_default("charset", "GBK");
            let db_user = param_object.get_value_by_env_name("DbUser");
            let db_passwd = param_object.get_value_by_env_name("DbPass");
            let extended_properties = param_object.get_value_by_env_name("extendedProperties");

            let mut db_url = format!( "mysql://{}:{}@{}:{}/{}?ssl-mode=DISABLED&serverTimezone=Asia/Shanghai&characterEncoding={}&useOldAliasMetadataBehavior=true",
                    db_user, db_passwd, db_host,db_port, db_name,charset
                );

            if extended_properties.is_empty() {
                let mut use_ssl = false;
                let extend_properties: Vec<String> = Vec::default();
                extend_properties.iter().for_each(|prop| {
                    if prop.starts_with("useSSL") {
                        use_ssl = true;
                    }
                    db_url.push('&');
                    db_url.push_str(prop.as_str());
                });
                if !use_ssl {
                    db_url.push_str("&useSSL=false");
                }
            } else {
                db_url.push_str(extended_properties);
            }
            (db_name, db_url)
        } else {
            let db_account = db_account.expect("db_acount used to init conn must not be none");

            let db_host = db_account.host.as_str();
            let db_port = db_account.port;
            let db_name = db_account.db_name.as_str();
            let charset = db_account.charset.as_str();
            let db_user = db_account.username.as_str();
            let db_passwd = db_account.password.as_str();

            let mut db_url = format!( "mysql://{}:{}@{}:{}/{}?ssl-mode=DISABLED&serverTimezone=Asia/Shanghai&characterEncoding={}&useOldAliasMetadataBehavior=true",
                    db_user, db_passwd, db_host,db_port, db_name,charset
                );

            let mut use_ssl = false;
            let extend_properties: Vec<String> = Vec::default();
            extend_properties.iter().for_each(|prop| {
                if prop.starts_with("useSSL") {
                    use_ssl = true;
                }
                db_url.push('&');
                db_url.push_str(prop.as_str());
            });
            if !use_ssl {
                db_url.push_str("&useSSL=false");
            }
            (db_name, db_url)
        };

        debug!("try to connect database: {}", db_url);
        let mysql_conn = MySqlConnection::connect(db_url.as_str()).await?;
        debug!("success connected");

        db_connection.conn = Some(mysql_conn);

        param_object.set_value_by_env_name("dbOwner", db_name.to_owned());
        Ok(db_connection)
    }
}
